package org.gikolet.ch2.android.io;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.SequenceInputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Date;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.input.CountingInputStream;
import org.apache.commons.io.input.TeeInputStream;
import org.gikolet.ch2.data.Board;
import org.gikolet.ch2.data.Res;
import org.gikolet.ch2.data.ThreadHeader;
import org.gikolet.ch2.io.ResesReader;
import org.gikolet.ch2.io.Storage;
import org.gikolet.ch2.logic.AboneException;
import org.gikolet.ch2.logic.Ch2Exception;
import org.gikolet.ch2.logic.NotModifiedException;
import org.gikolet.util.IOUtils;

public class Ch2AndLocalDatReader implements ResesReader {

	private ThreadHeader threadHeader;

	private File file;
	private File tmpFile;

	private HttpURLConnection http;

	private Ch2DatInputStreamParser ch2DatParser;

	private CountingInputStream countFileIn;
	private CountingInputStream countHttpIn;

	private boolean error = false;
	private boolean end = false;

	public Ch2AndLocalDatReader(ThreadHeader threadHeader, Storage storage,
			String charsetName) throws NotModifiedException, AboneException,
			Ch2Exception, IllegalCharsetNameException,
			UnsupportedCharsetException, IOException {

		this(threadHeader, storage, Charset.forName(charsetName));
	}

	public Ch2AndLocalDatReader(ThreadHeader threadHeader, Storage storage,
			Charset charset) throws NotModifiedException, AboneException,
			Ch2Exception, IOException {

		this.threadHeader = threadHeader;
		Board board = threadHeader.getBoard();

		// TODO 抽象化が必要かも
		URL url = new URL("http://" + board.getHostName() + "/"
				+ board.getPath() + "/dat/" + threadHeader.getThreadKey()
				+ ".dat");

		this.file = storage.getThreadFile(threadHeader);
		this.tmpFile = File.createTempFile(threadHeader.getThreadKey(), ".dat",
				storage.getTempDirectory());

		InputStream fileIn = null;
		OutputStream fileOut = null;
		InputStream httpIn = null;
		InputStream in = null;

		try {

			this.http = (HttpURLConnection) url.openConnection();
			this.http.setRequestMethod("GET");

			Long byteSize = threadHeader.getByteSize();
			Date lastModified = threadHeader.getLastModified();

			// datファイルが存在し、キャッシュと整合性がとれているか
			if (this.file.isFile() && byteSize != null
					&& byteSize == this.file.length() && lastModified != null) {
				FileUtils.copyFile(this.file, this.tmpFile);

				this.countFileIn = new CountingInputStream(new FileInputStream(
						this.tmpFile));

				fileIn = this.countFileIn;

				this.http.setIfModifiedSince(lastModified.getTime());

				this.http
						.setRequestProperty("Range", "bytes=" + byteSize + "-");
			}

			this.http.connect();

			int resCode = this.http.getResponseCode();

			switch (resCode) {
			case HttpURLConnection.HTTP_NOT_MODIFIED:
				throw new NotModifiedException("Http response code is +304.");
			case 416:
				throw new AboneException("Http response code is +416.");

			case HttpURLConnection.HTTP_OK:
			case 206:
				break;

			default:
				throw new Ch2Exception("responce code(=" + resCode
						+ ") is invalid");
			}

			this.countHttpIn = new CountingInputStream(this.http
					.getInputStream());

			httpIn = this.countHttpIn;

			fileOut = new BufferedOutputStream(new FileOutputStream(
					this.tmpFile, true));
			httpIn = new TeeInputStream(httpIn, fileOut, true);

			in = httpIn;

			// ファイル入力が存在するか
			if (fileIn != null) {
				in = new SequenceInputStream(fileIn, in);
			}

			this.ch2DatParser = new Ch2DatInputStreamParser(in, charset);

		} catch (IOException e) {
			IOUtils.closeQuietly(in);
			IOUtils.closeQuietly(httpIn);
			IOUtils.closeQuietly(fileIn);
			IOUtils.closeQuietly(fileOut);

			throw e;
		}
	}

	public String getSubject() throws IOException {
		return this.ch2DatParser.getSubject();
	}

	public Res read() throws IOException {
		try {
			Res res = this.ch2DatParser.read();
			if (res == null) {
				this.end = true;
			}
			return res;

		} catch (IOException e) {
			this.error = true;
			throw e;

		} catch (RuntimeException e) {
			this.error = true;
			throw e;

		} catch (Error e) {
			this.error = true;
			throw e;
		}
	}

	public void close() throws IOException {
		try {
			this.ch2DatParser.close();

			if (!this.error && this.end) {
				// トランザクション使いたいよぅ
				FileUtils.deleteQuietly(this.file);
				FileUtils.moveFile(this.tmpFile, this.file);

				this.threadHeader.setByteSize(getReadContentLength());
				this.threadHeader.setLastModified(new Date(this.http
						.getLastModified()));
				this.threadHeader.setResCount(getReadResCount());
				this.threadHeader.setSubject(getSubject());

			} else {
				this.tmpFile.delete();
			}

		} catch (IOException e) {
			e.printStackTrace();

		} finally {
			this.http.disconnect();
		}
	}

	public long getContentLength() {
		long cl = this.http.getContentLength();
		if (cl != -1) {
			return this.file.length() + this.http.getContentLength();
		}
		return -1;
	}

	public long getReadContentLength() {
		return this.ch2DatParser.getReadContentLength();
	}

	public int getReadResCount() {
		return this.ch2DatParser.getReadResCount();
	}
}
